These exercises require the latest version of popdemo. Head to popdemo’s GitHub (github.com/iainmstott/popdemo) for installation instructions (don’t forget also to load the package using library(popdemo)!)
Complete the core exercises (in normal print) first, as code in each section continues from the last. Afterward, return to the “extras” sections (in italics), and try writing your own code. Code to be run is in chunks:
Key terms in the text are in bold italic, and functions or arguments are fixed width.
We will use a matrix projection model (MPM) to explore population dynamics for the desert tortoise Gopherus agassizzii, with medium fecundity1. The population is found in the Mojave desert, USA. There are 8 stages are based on age and size (carapace length in mm):
- Yearling (age 0-1)
- Juvenile 1 (<60 mm)
- Juvenile 2 (90-99mm)
- Immature 1 (100-139mm)
- Immature 2 (140-179mm)
- Subadult (180-207mm)
- Adult 1 (208-239mm)
- Adult 2 (>240mm)
Load in the data:
data(Tort); Tort
## Yr J1 J2 I1 I2 SA A1 A2
## Yr 0.000 0.000 0.000 0.000 0.000 1.300 1.980 2.57
## J1 0.716 0.567 0.000 0.000 0.000 0.000 0.000 0.00
## J2 0.000 0.149 0.567 0.000 0.000 0.000 0.000 0.00
## I1 0.000 0.000 0.149 0.604 0.000 0.000 0.000 0.00
## I2 0.000 0.000 0.000 0.235 0.560 0.000 0.000 0.00
## SA 0.000 0.000 0.000 0.000 0.225 0.678 0.000 0.00
## A1 0.000 0.000 0.000 0.000 0.000 0.249 0.851 0.00
## A2 0.000 0.000 0.000 0.000 0.000 0.000 0.016 0.86The numbers in the matrix (called matrix elements or transitions) describe the probability of moving FROM stages in each column TO stages in each row, within the time interval chosen. For example, for this desert tortoise matrix, in any year a subadult (stage 6) has approimately 24.9% probability of becoming an adult (stage 7): this may be called a growth or progression transition. Likewise, in any year a subadult has about 67.8% chance of staying a subadult: this is called a stasis transition. This means that 100 - (67.8 + 24.9) = 7.3% of subadults die every year. There are different types of transitions: in this matrix there are also fecundity transitions which describe offspring production, and subadults produce on average 1.3 offspring per year. Other species may have different transitions, including skipping stages through fast growth, shrinkage or fission (especially in modular organisms, e.g. most plants, corals), or asexual reproduction.
Matrix elements combine underlying vital rates such as survival, growth and reproduction. For example, the subadult to adult transition (24.9%) combines probability of growing to adult size in one year, and probability of surviving the year. The subadult fecundity (1.3) combines the average number of offspring produced by subadults per year, and probability that those offspring survive the year.
A baby desert tortoise
The core function for understanding population dynamics is the project function. It can be used to understand a number of different types of population dynamics, including deterministic models, stochastic models, long-term dynamics and short-term dynamics.
In a deterministic model, there is no density dependence, and no stochasticity: the vital rates of the model, and therefore the matrix elements, don’t change from timestep to timestep (in a density dependent model, they would change according to the size of all or part of the population, and in a stochastic model they would change randomly at each iteration of the model to incorporate random environmental or demographic variation). The projected population dynamics (time series of population size and structure over time) come from multiplying the matrix and the starting poulation vector:
\[\textbf{n}_\text{t} = \textbf{A}^t\textbf{n}_0\]
That is, the population size at time t nt is equal to projection matrix A multiplied by the population size at time 0, n0. We have the matrix, but not a vector. We will:
- choose a vector using a random uniform distribution
- project this vector using the project function from popdemo
- plot the projection
Tortvec1 <- runif(8)
Tortvec1 <- Tortvec1/sum(Tortvec1) #scales the vector to sum to 1
( Tortp1.1 <- project(Tort, Tortvec1, time = 100) )
## 1 deterministic population projection over 100 time intervals.
##
## [1] 1.00000 1.38136 1.62106 1.76709 1.84562 1.87337 1.86187 1.82053
## [9] 1.75779 1.68133 1.59773 1.51230 1.42893 1.35016 1.27738 1.21108
## [17] 1.15111 1.09689 1.04765 1.00255 0.96079 0.92167 0.88465 0.84929
## [25] 0.81530 0.78249 0.75074 0.72002 0.69031 0.66161 0.63394 0.60731
## [33] 0.58172 0.55718 0.53365 0.51112 0.48957 0.46894 0.44921 0.43032
## [41] 0.41225 0.39496 0.37840 0.36254 0.34735 0.33279 0.31885 0.30548
## [49] 0.29268 0.28041 0.26865 0.25739 0.24659 0.23625 0.22634 0.21684
## [57] 0.20775 0.19903 0.19069 0.18269 0.17503 0.16768 0.16065 0.15391
## [65] 0.14746 0.14127 0.13535 0.12967 0.12423 0.11902 0.11403 0.10925
## [73] 0.10467 0.10028 0.09607 0.09204 0.08818 0.08448 0.08094 0.07755
## [81] 0.07429 0.07118 0.06819 0.06533 0.06259 0.05997 0.05745 0.05504
## [89] 0.05273 0.05052 0.04840 0.04637 0.04443 0.04256 0.04078 0.03907
## [97] 0.03743 0.03586 0.03436 0.03292 0.03154
plot(Tortp1.1, log = "y")The project function should usually have a matrix passed to the A argument, and a vector, which can be a single vector, multiple column vectors in a matrix, "n" (a default option which projects a set of stage-biased vectors) or "diri" (which projects large set of random vectors). We will encounter all these options in this vignette. The time argument gives the number of projection intervals, but the output will have length time + 1, because the population size at time 0 is included.
The project function returns an object of class ‘Projection’, containing the overall population size over time. In this case, this is a single vector but for objects containing multiple projections (as we’ll encounter later), the projections are held in a matrix with one column per projection (therefore the number of rows is equal to time + 1).
The ‘Projection’ object also includes information on the time series of population vectors (i.e. (st)age-specific number / density). For an object containing a single projection, each row represents one timestep and each column represents one (st)age. We will access vectors for time intervals from 0 to 10:
vec(Tortp1.1)[1:11, ]
## Yr J1 J2 I1 I2 SA A1 A2
## [1,] 0.08003 0.1556 0.10020 0.24927 0.10731 0.09178 0.1665 0.04933
## [2,] 0.57569 0.1455 0.08000 0.16549 0.11867 0.08637 0.1645 0.04509
## [3,] 0.55390 0.4947 0.06704 0.11188 0.10535 0.08526 0.1615 0.04141
## [4,] 0.53704 0.6771 0.11173 0.07756 0.08529 0.08151 0.1587 0.03819
## [5,] 0.51830 0.7684 0.16424 0.06350 0.06599 0.07445 0.1553 0.03539
## [6,] 0.49528 0.8068 0.20762 0.06282 0.05187 0.06533 0.1507 0.03292
## [7,] 0.46795 0.8121 0.23793 0.06888 0.04381 0.05596 0.1445 0.03072
## [8,] 0.43788 0.7955 0.25591 0.07706 0.04072 0.04780 0.1369 0.02873
## [9,] 0.40711 0.7646 0.26363 0.08467 0.04091 0.04157 0.1284 0.02690
## [10,] 0.37747 0.7250 0.26340 0.09042 0.04281 0.03739 0.1196 0.02519
## [11,] 0.35024 0.6813 0.25737 0.09386 0.04522 0.03498 0.1111 0.02358The time series of stage 2 sizes is:
vec(Tortp1.1)[ , 2]
## [1] 0.15561 0.14553 0.49471 0.67709 0.76844 0.80681 0.81208 0.79550
## [9] 0.76457 0.72500 0.68134 0.63710 0.59473 0.55576 0.52094 0.49040
## [17] 0.46386 0.44082 0.42064 0.40270 0.38644 0.37138 0.35717 0.34354
## [25] 0.33033 0.31746 0.30487 0.29257 0.28059 0.26895 0.25767 0.24680
## [33] 0.23634 0.22630 0.21669 0.20749 0.19870 0.19031 0.18228 0.17461
## [41] 0.16728 0.16026 0.15355 0.14712 0.14096 0.13505 0.12940 0.12398
## [49] 0.11878 0.11381 0.10903 0.10446 0.10008 0.09588 0.09186 0.08801
## [57] 0.08431 0.08078 0.07739 0.07414 0.07103 0.06805 0.06520 0.06246
## [65] 0.05985 0.05734 0.05493 0.05263 0.05042 0.04831 0.04628 0.04434
## [73] 0.04248 0.04070 0.03899 0.03735 0.03579 0.03429 0.03285 0.03147
## [81] 0.03015 0.02889 0.02768 0.02651 0.02540 0.02434 0.02332 0.02234
## [89] 0.02140 0.02050 0.01964 0.01882 0.01803 0.01727 0.01655 0.01586
## [97] 0.01519 0.01455 0.01394 0.01336 0.01280When a Projection object contains multiple projections, the vectors are contained in a 3-dimensional array. As for a single projection, the first dimension is time and the second is (st)age, whilst the third represents each separate population projection. Therefore, for example, the first 10 timesteps of the third projection would be accessed using object[1:11, ,3]; the sizes of the second stage for all projections would be accessed using object[ , 2, ]; the size of the second stage at the 10th timestep of the second stage in the third projection would be object[1:11, 2, 3].
Population growth in an MPM is geometric: when you plot population size on a log scale (as we have here), it’s easy to see that the population settles to a stable geometric rate of decline. At this point, the population also has a fixed structure: the relative numbers of individuals in each stage don’t change (although the absolute numbers do; this is what causes the population decline). These stable long-term dynamics are often called asymptotic dynamics. The short-term dynamics that happen before this stable state is reached and are different to asymptotic dynamics are called transient dynamics. In this exercise we’ll explore both asymptotic and transient dynamics of deterministic MPM models.
ii. EXTRAS…
’Projection objects contain further information that can be accessed in similar ways to the vec() function. Type ?Projection-class to see further options.
Try altering the parameters in the project function: change the amount of time the model is projected using the time argument, or change the population vector using the vector argument. Have a look at how the asymptotic dynamics don’t change as the vector changes, but observe how the transient dynamics change. See whether changing the vector changes the amount of time taken to reach stable state. Type ?project to see further options (some of them will be covered in this vignette).
The plot function takes any of the usual graphical parameters: try changing the lines to points (type argument), changing the line colour, type or thickness(col, lty, lwd), changing the box around the plot (bty), or any other graphical parameters (see ?par). There are further options available specifically for ‘Projection’ objects: see ?Projection-plots.
In the long term, the population dynamics are described by the dominant eigendata of the matrix:
eigs(Tort, "all")
## $lambda
## [1] 0.9581
##
## $ss
## [1] 0.22166 0.40585 0.15463 0.06508 0.03842 0.03087 0.07179 0.01171
##
## $rv
## [1] 0.1955 0.2616 0.6866 1.8019 2.7148 4.8029 4.3813 5.1237The eigs function returns the dominant eigendata (or “eigenstuff”) of the matrix2. The growth rate is commonly referred to as lambda (\(\lambda\)). In this case, \(\lambda\) < 1 which means the population declines. If \(\lambda\) = 1 the population neither grows nor declines, and If \(\lambda\) > 1, the population grows.
The rest of the eigenstuff describes other aspects of stable dynamics. “ss” refers to the stable structure: this is the ratio of numbers of individuals in each stage once the population reaches stable state, and the vector is usually denoted with w. A population with this structure, then it will grow/decline at the stable rate from the outset (populations starting with a stable structure will always be shown with dashed lines):
w is scaled so that ||w||1 = 1.3
“rv” refers to the reproductive value vector, and is usually denoted with v. This is the contribution that each stage makes to stable growth (through survival, growth and reproduction). Stages with high current and reproduction and survival have high reproductive value. v is scaled so that vTw = 14 when ||w||1 = 1.
iii. EXTRAS…
The eigs function allows you to choose what eigenstuff you want to calculate. Try replacing “all” with one or more of “lambda”, “ss”, or “rv”. If you want to look at subdominant eigenstuff then the base function “eigen” does this, with eigen(A) giving the right eigenvectors and eigen(t(A)) giving the left eigenvectors.
Before settling to stable growth rate, a population will grow, decline or fluctuate in growth at rates faster and/or slower than asymptotic growth. We can see this in our plot of population dynamics of the desert tortoise. These population dynamics are called transient dynamics and are a little harder to characterise than asymptotic dynamics as they’re so variable and depend on the population structure5.
popdemo contains functions that calculate deviations from stable growth at various points along the population projection. These transient indices make two standardisations: starting population vector n0 is scaled by ||n0||1 so that it sums to 1:
\[\hat{\textbf{n}}_0 = \frac{\textbf{n}_0}{||\textbf{n}_0||_1}\]
The projection matrix is scaled by \(\lambda\), so that lambda of the scaled matrix becomes 1:
\[\hat{\textbf{A}} = \frac{\textbf{A}}{\lambda}\]
These standardisations allow comparison of transient dynamics between populations with different sizes, and with different long-term dynamics. We can visualise this this in a standardised population projection:
Tortp1.1s <- project(Tort, Tortvec1, time = 100,
standard.A = TRUE, standard.vec = TRUE)
Tortpws <- project(Tort, Tortw, time = 100,
standard.A = TRUE, standard.vec = TRUE)
plot(Tortp1.1s, log = "y")
lines(Tortpws, lty = 2)Transient dynamics vary according to the starting population vector. If there is an overrepresentation of individuals in stages with high survival and/or fertility, then the population will grow faster (or decline slower) than the stable rate (this is called ‘amplification’). If there is an overrepresentation of individuals in stages with low survival and zero/low fertility, then the population will grow slower (or decline faster) than the stable rate (this is called ‘attenuation’). Indices of amplification are always >1, which means that log-transformed indices of amplification are always >0. Indices of attenuation are always <1 but >0. This means that log-transformed indices of attenuation are always <0.
We can measure transient density at any time along the projection (we say “density” because a standardised dynamic is no longer directly equivalent to size… but this is not the same thing as spatial density!). But in comparative analysis, to make things comparable between populations, we can use comparable timepoints.
Two possibilities are at t = 1 and at \(t\to\infty\). These indices are called reactivity and inertia respectively:
( r1 <- reac(Tort, Tortvec1) )
## [1] 1.442
( i1 <- inertia(Tort, Tortvec1) )
## [1] 2.289
points(c(1, 100), c(r1, i1), pch = 3, col = "red")Reactivity (r1) is the population size in the first timestep of the projection (one year), relative to a population with stable growth. A reactivity of 2 would mean that in the first timestep, the population grows twice as fast as its stable growth rate. Reactivity is calculated using:
\[\textit{reactivity} = P_1 = ||\hat{\textbf{A}} \hat{\textbf{n}}_0||_1\]
As we can see, measurements of transient dynamics use the standardisations we encountered earlier. These “standardisations” mean that transient indices are measured relative to long-term population growth, and initial population size. The interpretation of this is that transient indices measure the ratio of population size compared to a population growing at a stable rate.
Inertia (i1) is the ratio of the size of the population in the long-term, relative to a population with stable growth. An inertia of 4 would mean that after the transient period, the population settles to a size 4 times as large as a population that grows with stable rate. Inertia is calculated using:
\[\textit{inertia} = P_\infty = \frac{\textbf{v}^\text{T}\hat{\textbf{n}}_0||\textbf{w}||_1}{\textbf{v}^\text{T}\textbf{w}}\]
w and v are the right and left eigenvectors as described in the “Asymptotic dynamics” section above. If w and v are scaled as described, then \(\textit{inertia} = \textbf{v}^\text{T}\hat{\textbf{n}}_0\).
Comparable points at which to measure transient dynamics don’t have to be at the same time of the projection. maximum amplification and maximum attenuation describe the largest and smallest population sizes (relative to long-term growth \(\lambda\)). Maximum amplification and maximum attenuation can occur at any point along the projection. Use return.t = TRUE to return the time at which they occur, as well as their value.
Some populations only have a maximum amplification (because they never attenuate), some only have a maximum attenuation (because they never amplify), and some have both. With this proviso in mind, these indices are calculated using: \[maximum \ amplification = \bar{P}_{max} = \max_{t > 0}||\hat{\textbf{A}}^t\hat{\textbf{n}}_0||_1\] \[maximum \ attenuation = \underline{P}_{min} = \min_{t > 0}||\hat{\textbf{A}}^t\hat{\textbf{n}}_0||_1\]
If we want to compare several different population structures, then it’s possible to project several simultaneously. We will:
- create two extra vectors; one which amplifies and one which attenuates
- bind these with the random vector into a matrix with 3 columns
- project the model with this matrix passed to the vec argument
- calculate reactivity, inertia and maximum amplification / attenuation for the extra vectors
- plot these on the graph
TortAMP <- c(1, 1, 2, 3, 5, 8, 13, 21) #a population that amplifies
TortATT <- c(21, 13, 8, 5, 3, 2, 1, 1) #a population that attenuates
TortBTH <- c(0, 0, 0, 1, 0, 0, 0, 0) #a population that does both
Tortvec3 <- cbind(AMP = TortAMP,
ATT = TortATT,
BTH = TortBTH)
Tortp3.1 <- project(Tort, Tortvec3, time = 100,
standard.A = TRUE, standard.vec = TRUE)
plot(Tortp3.1, log = "y"); lines(Tortpws, lty = 2)
( r3 <- apply(Tortvec3, 2, reac, A = Tort) )
## AMP ATT BTH
## 2.6319 0.9153 0.8757
( r3t <- rep(1, 3) )
## [1] 1 1 1
( i3 <- apply(Tortvec3, 2, inertia, A = Tort) )
## AMP ATT BTH
## 4.1442 0.9123 1.8019
( i3t <- rep(100, 3) )
## [1] 100 100 100
( max3 <- apply(Tortvec3[,c(1,3)], 2, maxamp, A = Tort) )
## AMP BTH
## 5.111 1.962
( max3t <- apply(Tortvec3[,c(1,3)], 2, function(x){
maxamp(vector = x, A = Tort, return.t = TRUE)$t}) )
## AMP BTH
## 6 13
( min3 <- apply(Tortvec3[,c(2,3)], 2, maxatt, A = Tort) )
## ATT BTH
## 0.8377 0.7261
( min3t <- apply(Tortvec3[,c(2,3)], 2, function(x){
maxatt(vector = x, A = Tort, return.t = TRUE )$t}) )
## ATT BTH
## 4 3
points(c(r3t, i3t, max3t, min3t),
c(r3, i3, max3, min3),
pch = 3, col = "red")
iv. EXTRAS…
Some further functionality offered by the reac, inertia, maxamp and maxatt functions. Try using, for example, return.N = TRUE to give the transient population size (including influences of initial population size and asymptotic population growth), and use this to plot transient indices on non-standardised population projections.
Transient dynamics are very variable, and there are an infinite number of different starting population vectors. Sometimes we don’t know what the population structure is (making a fair census of plants and animals is difficult!), but it is possible to get an idea of what the transient dynamics will be like. Transient bounds capture the outer extremes of transient dynamics; all population trajectories lie within the bounds.
It’s easy to plot the bounds by adding bounds = TRUE when plotting a population projection:
The bounds are calculated from the set of projections of “stage-biased” vectors, each of which has 100% of individuals in one stage. For example, the stage-biased vectors for a 3-by-3 matrix are [1,0,0], [0,1,0] and [0,0,1]. To project these, use vector = "n"; this is the default value, so in fact if vector isn’t set then the stage-biased vectors will be projected automatically:
The top and bottom lines are the bounds on population dynamics. No deterministic projection can be outside these lines.
When calculating transient indices, use bound = "upper" or bound = "lower" to calculate bounds on reactivity and inertia. To calculate bounds on maximum amplification or attenuation, just don’t pass anything to vector:
plot(Tortp3.1, log = "y", bounds = TRUE)
lines(Tortpws, lty = 2)
( ruprB <- reac(Tort, bound = "upper") )
## [1] 3.58
( rlwrB <- reac(Tort, bound = "lower") )
## [1] 0.7473
( iuprB <- inertia(Tort, bound = "upper") )
## [1] 5.124
( ilwrB <- inertia(Tort, bound = "lower") )
## [1] 0.1955
( maxB <- maxamp(Tort, return.t = TRUE) )
## $maxamp
## [1] 6.83
##
## $t
## [1] 5
( minB <- maxatt(Tort, return.t = TRUE) )
## $maxatt
## [1] 0.1228
##
## $t
## [1] 9
points(c(rep(1, 5), rep(100, 5), max3t, maxB$t, min3t, minB$t),
c(r3, ruprB, rlwrB, i3, iuprB, ilwrB, max3, maxB$maxamp, min3, minB$maxatt),
pch = 3, col = "red",
lwd = c(rep(c(1, 1, 1, 2, 2), 2), rep(c(1, 1, 2) ,2 )) )Using \(\rho_1\) to refer to reactivity bounds, \(\rho_\infty\) to refer to inertia bounds, \(\rho_{max}\) to refer to the maxmimum amplification bound, \(\rho_{min}\) to refer to the maximum attenuation bound, and with overbars to refer to upper bounds and underbars to refer to lower bounds6:
\[\bar{\rho}_1 = ||\hat{\textbf{A}}||_1 \hspace{0.75cm} and \hspace{0.75cm} \underline{\rho}_1 = minCS(\hat{\textbf{A}})\] \[\bar{\rho}_\infty = \frac{v_{max}||\textbf{w}||_1}{\textbf{v}^\text{T}\textbf{w}} \hspace{0.75cm} and \hspace{0.75cm} \underline{\rho}_\infty = \frac{v_{min}||\textbf{w}||_1}{\textbf{v}^\text{T}\textbf{w}}.\] \[\bar{\rho}_{max} = \max_{t > 0}||\hat{\textbf{A}}^t||_1 \hspace{0.75cm} and \hspace{0.75cm} \underline{\rho}_{min} = \min_{t > 0}(minCS(\hat{\textbf{A}}^t))\]
When w and v are scaled as decribed in the “Asymptotic dynamics” section, then \(\bar{\rho}_\infty = v_{max}\) and \(\bar{\rho}_\infty = v_{min}\).
v. EXTRAS…
We’ve seen a little bit of the diversity of different transient dynamics that can be shown by a model. But there are an infinite number of different possible starting population vectors. If population vectors are drawn at random, it is possible to shade in the space within the transient bounds to work out the likelihood of different transient densities. This can be done by using vector = "diri" in the project function, which draws populations at random from a dirichlet distribution and projects them. Plot this using plottype = "shady":
Tortpd <- project(Tort, "diri", time = 31,
standard.A = TRUE)
plot(Tortpd, plottype = "shady", bounds = T, log = "y")Dirichlet projections have their own options within the project and plot functions: see the “Projection-class” and “project” help pages.
for a detailed introduction to matrix models and eigendata, see Caswell (2001) Matrix Population Models, Sinauer.↩
||w||1 is the one-norm of w, which is equal to its sum.↩
vT is the transpose of v: not to be confused with an exponent!↩
Stott et al. (2011) Ecol. Lett., 14, 959-970 contains a review on measuring transient dynamics in MPMs.↩
\(||\hat{\textbf{A}}||_1\) is the one-norm of \(\hat{\textbf{A}}\), is equal to its maximum column sum. \(minCS\) describes the minimum column sum. \(v_{max}\) and \(v_{min}\) are the maximum and minimum entries of v, respectively.↩